/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// sv_spawn.cpp - server spawning
//

// TODO:
// - add support for pure packs later


#include "sv_local.h"


/*
 ==============================================================================

 CLIENT UPDATING

 ==============================================================================
*/


/*
 ==================
 SV_UpdateMaxClients
 ==================
*/
static void SV_UpdateMaxClients (){

	client_t	*client, *clients;
	int			maxClients;
	int			i;

	maxClients = sv_maxClients->integerValue;

	// Update sv_maxClients variable
	CVar_UpdateLatched(sv_maxClients);

	// Guarantee slots for all connected clients
	if (sv_maxClients->integerValue < sv_numClients->integerValue)
		CVar_SetInteger(sv_maxClients, sv_numClients->integerValue);

	// Allow the code to detect changes to sv_maxClients variable
	sv_maxClients->modified = false;

	// Check if it changed at all
	if (sv_maxClients->integerValue == maxClients)
		return;

	// Copy all connected clients to a temporary buffer
	clients = client = (client_t *)Mem_Alloc(sv_numClients->integerValue * sizeof(client_t), TAG_TEMPORARY);

	for (i = 0; i < maxClients; i++){
		if (svs.clients[i].state < CS_CONNECTED)
			continue;

		Mem_Copy(client, &svs.clients[i], sizeof(client_t));

		client->clientNum = client - clients;

		client++;
	}

	// Reallocate the client slots
	Mem_Free(svs.clients);

	svs.clients = (client_t *)Mem_ClearedAlloc(sv_maxClients->integerValue * sizeof(client_t), TAG_COMMON);

	Mem_Copy(svs.clients, clients, sv_numClients->integerValue * sizeof(client_t));

	// Free the temporary buffer
	Mem_Free(clients);
}

/*
 ==================
 SV_ReconnectClients
 ==================
*/
static void SV_ReconnectClients (){

	client_t	*client;
	const char	*reason;
	int			i;

	for (i = 0, client = svs.clients; i < sv_maxClients->integerValue; i++, client++){
		if (client->state < CS_CONNECTED)
			continue;

		// Reconnect the client
		reason = glqSGame->ClientConnect(client->clientNum, NET_IsLocalAddress(client->netChan.remoteAddress), NET_AddressToString(client->netChan.remoteAddress), client->userInfo, false);

		// This generally shouldn't happen, because the client was connected
		// before the level change
		if (reason){
			SV_DropClient(client, reason);
			continue;
		}

		SV_UserInfoChanged(client);

		// Force a new server state to be sent
		client->state = CS_CONNECTED;

		client->serverId = 0;

		client->pureAuthentic = false;

		client->serverStateMessage = -1;

		client->gameStateMessage = -1;
		client->gameStateOffset = -1;

		client->nextPacketTime = svs.time;			// Send a packet immediately

		client->lastConnectTime = svs.time;			// Don't allow a reconnect for a few seconds
		client->lastReceivedTime = svs.time;		// Don't time-out

		client->timeOutCount = 0;
	}
}


/*
 ==============================================================================

 SERVER SPAWNING

 ==============================================================================
*/


/*
 ==================
 SV_Startup

 Called only if the server wasn't running before
 ==================
*/
static void SV_Startup (){

	// Update latched variables
	CVar_UpdateLatched(sv_pure);
	CVar_UpdateLatched(sv_voiceChat);
	CVar_UpdateLatched(sv_maxClients);
	CVar_UpdateLatched(sv_lanServer);

	// Allow the code to detect changes to latched variables
	sv_pure->modified = false;
	sv_voiceChat->modified = false;
	sv_maxClients->modified = false;
	sv_lanServer->modified = false;

	// Set an initial seed for the random number generator
	svs.random.SetSeed(Sys_Milliseconds());

	// Allocate client slots
	svs.clients = (client_t *)Mem_ClearedAlloc(sv_maxClients->integerValue * sizeof(client_t), TAG_COMMON);

	// Now active
	svs.initialized = true;

	// Set the com_serverRunning variable
	CVar_SetBool(com_serverRunning, true);
}

/*
 ==================
 SV_Cleanup

 Called only if the server was running before
 ==================
*/
static void SV_Cleanup (){

	client_t	*client;
	int			i;

	Com_Printf("Changing level...\n");

	// Shutdown game module
	SG_Shutdown();

	// Update max clients
	SV_UpdateMaxClients();

	// Send a changing message to all connected clients
	for (i = 0, client = svs.clients; i < sv_maxClients->integerValue; i++, client++){
		if (client->state < CS_CONNECTED)
			continue;

		// Not active anymore, but not disconnected
		client->state = CS_CONNECTED;

		// If the client is downloading, let it finish
		if (client->downloadChecksum)
			continue;

		// Don't send a changing message to the local client
		if (NET_IsLocalAddress(client->netChan.remoteAddress))
			continue;

		SV_AddReliableCommand(client, "changing");

		// Force a new packet to be sent
		SV_SendPacketToClient(client, true);
	}
}

/*
 ==================
 SV_InitServer
 ==================
*/
static void SV_InitServer (const char *levelName, bool allowCheats){

	int		i;

	Com_Printf("-------- Server Initialization --------\n");
	Com_Printf("Map: %s\n", levelName);
	Com_Printf("Server is %s\n", (sv_pure->integerValue) ? "pure" : "unpure");
	Com_Printf("Cheats are %s\n", (allowCheats) ? "enabled" : "disabled");

	// Wipe the entire serverActive_t structure
	Mem_Fill(&sv, 0, sizeof(serverActive_t));

	// Set up the serverActive_t structure
	sv.serverId = com_frameTime;

	// Update server info variables
	CVar_SetInteger(sv_serverId, sv.serverId);
	CVar_SetString(sv_map, levelName);
	CVar_SetBool(sv_allowCheats, allowCheats);

	// Initialize game module
	SG_Init();

	// Run a few frames to allow everything to settle
	for (i = 0; i < 3; i++){
		svs.time += GAME_FRAMEMSEC;

		glqSGame->RunFrame();
	}

	// Create baselines for more efficient communications
	glqSGame->CreateBaselines();

	// Reconnect all the clients that were connected before the level change
	SV_ReconnectClients();

	// Run another frame to allow things to look at all the players
	svs.time += GAME_FRAMEMSEC;

	glqSGame->RunFrame();

	// Send a heartbeat to the master server immediately
	svs.masterHeartbeatTime = svs.time - HEARTBEAT_TIMEOUT;

	Com_Printf("---------------------------------------\n");
}

/*
 ==================
 SV_SpawnServer
 ==================
*/
void SV_SpawnServer (const char *levelName, bool allowCheats){

	char	name[MAX_PATH_LENGTH];

	// Make sure the map exists
	Str_SPrintf(name, sizeof(name), "maps/%s.bsp", levelName);

	if (FS_FindFile(name, NULL) == FIND_NO){
		Com_Printf("Map %s not found\n", name);
		return;
	}

	// Set up some stuff, or clear stuff from a previous level
	if (!svs.initialized)
		SV_Startup();
	else
		SV_Cleanup();

	// Connect the local client to the server
	CL_LocalConnect();

	// Shutdown all the client subsystems
	CL_ShutdownAll();

	// Free the clip map from a previous level
	CM_FreeMap();

	// Don't sync variables
	CVar_SyncVariables(NULL);

	// Set the cheat state
	CVar_FixCheatVariables(allowCheats);

	// Initialize a new server
	SV_InitServer(levelName, allowCheats);

	// Initialize all the client subsystems
	CL_InitAll();

	// If running a dedicated server
	if (com_dedicated->integerValue){
		// Flush file cache
		FS_FlushCache();

		// Free all temporary allocations
		Mem_FreeAll(TAG_TEMPORARY, true);

		// Make sure everything is paged in
		Mem_TouchMemory();
	}
}